Gradient text effects are pretty nifty and can add a nice element of visual flair to your posts (if I do say so myself). Adding this effect is pretty easy with CSS, but what if you want to only apply it to certain words inside a paragraph, heading, or similar block? In this post I’ll show you how you can add a gradient text effect that you can use for any block that accepts text content.
WordPress blocks have a concept for “text formats” to style parts of the text inside a given block. The most common examples would be things like bold and italic text but there are a number of text formats built-in to WordPress that text-based blocks can use including:
- Superscript
- Subscript
- Highlights (apply a specific color)
- Footnotes
- and more!
Here’s an example of what this looks like in the editor:

When I was building this site, I was interested in setting up gradient text to work in the same manner as a highlight. I already use this styling on the “dek” or intro to my posts, but I thought it would be cool to choose random words in a given sentence or headline and apply that gradient text effect to them.
We’ll start by explaining the CSS required and then dig into how to implement the new text format type.
Creating a gradient text effect with CSS
There are only a few properties required to create this effect: background-clip, background-image (for the gradient), and color.
Here’s a complete example that I’m using on this site:
.has-gradient-text,
.is-style-dek {
background-clip: text;
background-image: linear-gradient(to right, var(--theme-color-primary), var(--theme-color-secondary));
color: transparent;
}CSSThe thing that makes this all work is background-clip: text which confines the background to the text inside the element. From there, we’re using background-image to set the gradient and then we set the text color to transparent to allow the gradient to be visible.
So the exact colors will vary depending on your theme but otherwise these are the only CSS styles we’ll need to create the effect.
Defining the text format
Next we need to register our new text format and create a simple control for the block editor UI. Both of these things can easily be done inside of a single JavaScript file.
Note: As a general rule, I’d recommend you have a single file per text format you’re adding. It keeps all of the relevant code together and makes it super easy to copy/paste to new projects.
Before you begin, you’ll need to make sure you’re using a build process. My recommendation is to use @wordpress/scripts or 10up-toolkit to make this really easy.
Need help setting up your build process?
I previously wrote an article about setting up 10up-toolkit to bundle your scripts and I’ll be publishing another article soon on how to setup @wordpress/scripts. In the meantime, if you want to use @wordpress/scripts instead, you can check out the project documentation here.
To get started, create a file in your theme or plugin called gradient-text.js. If you’re using @wordpress/scripts or 10up-toolkit to build your JavaScript files, you should place this in whatever folder makes sense with your project setup. In my case, there’s a folder called “block-formats” where I have this file located in my theme.
There are two things we need to do in this file:
- Define our toolbar edit component so we can select the text format in the editor
- Register the text format with WordPress.
Importing necessary functions
Our next step is importing a few functions to use in our React component
import { registerFormatType, toggleFormat } from '@wordpress/rich-text';
import { RichTextToolbarButton } from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
JavaScriptregisterFormatType: This is used to register the format with WordPress and allow our format to appear in the editortoggleFormat: Used to handle changing the UI and updating the content when our format is enabled or disabledRichTextToolbarButton: This React component is used to display buttons in the Block Toolbar.useSelect: This function is used to get the state or settings for the editor.__: This translation function allows our text labels to be translatable. It’s optional, but highly recommended to ensure the text you enter is able to be translated (even if it isn’t right now)1.
These functions will be used in our toolbar component in the editor (i.e. the thing that you click on to select the format).
Creating the toolbar component
So our next step is to create a React function component that uses these functions to output a button in the Block Toolbar for us to use while editing posts and pages. This component will receive a few props automatically when we go to register the toolbar button:
isActive: determines if the text format is active or notonChange: What should happen when someone changes (i.e. enables or disables) the text formatvalue: The text the format should apply to
Props for React components come through as a single object, so we’re using destructuring to get specific keys out of that object to use in our component. This is a very common pattern in React and modern JavaScript in general.
import { registerFormatType, toggleFormat } from '@wordpress/rich-text';
import { RichTextToolbarButton } from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
function GradientTextControl ({ isActive, onChange, value }) {
// 1. Get the current selected block.
const selectedBlock = useSelect((select) => {
return select('core/block-editor').getSelectedBlock();
}, []);
// 2. Determine which blocks this format should be available for.
const allowedBlocks = ['core/paragraph', 'core/heading'];
// 3. Check if there is a block selected and if it's one of the allowed blocks.
if (selectedBlock && !allowedBlocks.includes(selectedBlock.name)) {
return null;
}
// 4. Output the button for the Block Toolbar that appears inside the dropdown menu.
return (
<RichTextToolbarButton
icon="editor-code"
title={ __( 'Gradient Text', 'lwptd' ) }
onClick={() => {
onChange(
toggleFormat(value, {
type: 'lwptd/gradient-text',
}),
);
}}
isActive={isActive}
/>
);
};
JavaScriptLet’s break down what’s happening above:
- We ask the editor to give us the currently selected block (if any). If no block is selected, the Block Toolbar won’t display so there’s nothing else to do.
- The selectedBlock variable will be null if no block is selected, or an object if one is selected.
- Here we’ve provided an array of registered blocks that should be able to use this format. If you didn’t include this and the conditional check below, the format would be available for any block, which is almost never what you want.
- Some blocks don’t use text formats or otherwise won’t work as expected if you tried to use this. In general, you should only apply things like this to specific blocks to avoid side-effects or unexpected behavior.
- The block names you add to the array should include the namespace as well as the block’s slug, as in the above example (namespace/slug).
- Now we check to see if there is a selected block and if so, if the block’s name is included in the list of allowed blocks. If not, then we return
nullwhich instructs React to not output anything for this component. - From here we output a
RichTextToolbarButtoncomponent with the props we need:icon: This is the icon that displays next to the button text in the Block Toolbar. You can use a dashicon name (like the editor-code) or you can provide your own SVG icon instead.title: The text for the toolbar button. This will display after the icon. Make sure you update the text domain fromlwptdto whatever your theme or plugin uses.onClick: What happens when someone clicks on this button.- We’re passing an anonymous function that calls
onChangefrom our component’s props. We then pass thetoggleFormatfunction we imported previously as a parameter for theonChangeand provide it the props it needs to toggle the format: thevalueprop and an object with the keytypeto indicate which format type is being toggled.
- We’re passing an anonymous function that calls
isActive: We pass theisActiveprop from our component into this component. If active, the button’s icon will have inverted colors (white on black) and the dropdown menu icon will be inverted to show you something is active inside of it.
The output of the above code will look like this:

Here’s how this will look when it’s active:

Adding the button outside of the dropdown (optional)
You may also add the format button in the editor toolbar directly without placing it inside the dropdown. The code is ultimately very similar and just requires you to use a few additional components and make a few key changes:
- Use the
ToolbarButtoncomponent instead ofRichTextToolbarButton - Output the button inside of
ToolbarGroupandBlockControls
Here’s an example of how this would look with our current setup:
import { registerFormatType, toggleFormat } from '@wordpress/rich-text';
import { BlockControls } from '@wordpress/block-editor';
import { ToolbarGroup, ToolbarButton } from '@wordpress/components';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
function GradientTextControl ({ isActive, onChange, value }) {
const selectedBlock = useSelect((select) => {
return select('core/block-editor').getSelectedBlock();
}, []);
const allowedBlocks = ['core/paragraph', 'core/heading'];
if (selectedBlock && !allowedBlocks.includes(selectedBlock.name)) {
return null;
}
return (
<BlockControls>
<ToolbarGroup>
<ToolbarButton
icon="editor-code"
title={ __( 'Gradient Text', 'lwptd' ) }
onClick={() => {
onChange(
toggleFormat(value, {
type: 'lwptd/gradient-text',
}),
);
}}
isActive={isActive}
/>
</ToolbarGroup>
</BlockControls>
);
};
JavaScriptHere’s how that would then appear in the toolbar:

Registering the new format
Now that we have our toolbar component, we just need to call registerFormatType to tell WordPress what to do with it.
import { registerFormatType, toggleFormat } from '@wordpress/rich-text';
import { RichTextToolbarButton } from '@wordpress/block-editor';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
function GradientTextControl ({ isActive, onChange, value }) {
const selectedBlock = useSelect((select) => {
return select('core/block-editor').getSelectedBlock();
}, []);
const allowedBlocks = ['core/paragraph', 'core/heading'];
if (selectedBlock && !allowedBlocks.includes(selectedBlock.name)) {
return null;
}
return (
<RichTextToolbarButton
icon="editor-code"
title={ __( 'Gradient Text', 'lwptd' ) }
onClick={() => {
onChange(
toggleFormat(value, {
type: 'lwptd/gradient-text',
}),
);
}}
isActive={isActive}
/>
);
};
registerFormatType('lwptd/gradient-text', {
title: __( 'Gradient Text', 'lwptd' ),
tagName: 'span',
className: 'has-gradient-text',
edit: GradientTextControl,
});
JavaScriptWhen you call registerFormatType you need to pass it two total parameters: the format name (including namespace) and the settings object. The settings object above includes a few key things:
title: The title of the text format. Should likely be the same as the button text.tagName: When you add a text format, the selected text is wrapped in an HTML element. Here you can specify which element to use.- When you’re adding a format for inline text (i.e. text that sits next to other text) you want to use an element that displays inline by default (such as a
span,i,strong,em, or similar.
- When you’re adding a format for inline text (i.e. text that sits next to other text) you want to use an element that displays inline by default (such as a
className: The HTML class that should be added to the tag.edit: The toolbar button component that displays in the editor. In our case, we’re using the function we defined earlier in the file.
Adding the block format to the editor
Now, we need to make sure that the JavaScript we just wrote makes its way into the editor. If you’re already bundling your JavaScript and extending other things in the block editor or creating your own blocks, you probably have a file that is loading in the editor for this purpose. If so, you just need to make sure this file is imported or otherwise bundled within that file.
If you don’t have such a file prepared already and are setting all of this up for the first time, you may want to set up a single entry file (i.e. a file output by your build tool that may be bundled with other files) which you can enqueue in the block editor.
Creating a new entry
I’ve found that it’s nice to have a single file for this kind of thing that imports other individual files. Here’s an example for how this might look:
- src/
- js/
- block-formats/
- gradient-text.js
- index.js
- core-block-overrides.jsPlaintextInside the src/js folder, you’ll have a folder called block-formats where all block formats you create will be defined inside their own file. That folder then has an index.js file (aka a “barrel file”) that will import each individual file inside of it:
import './gradient-text';JavaScriptThen the new core-block-overrides.js file imports that one index file:
import './block-formats'; // Imports block-formats/index.jsJavaScriptSo now any block format that is imported into src/js/block-formats/index.js will automatically be imported into this file. This is a really nice pattern that is especially relevant if you’re importing multiple types of overrides or extensions such as block variations, block styles, or other hooks to change things in the editor. Here’s how that might look with some other things in it to give you some context:
import './block-styles';
import './block-variations';
import './block-formats';JavaScriptThis is a professional pattern used on many big projects but you don’t explicitly have to do it this way. As long as you’re able to import the block format you create into the file, you’re all set!
If you’re making a new entry file, you’ll need to tell webpack (or whatever you’re using) to create this file. You can refer to the documentation for @wordpress/scripts, 10up-toolkit, or whatever build tool you’re using for how to do this (there’s a few different ways per tool but it’s pretty straightforward).
The key is to make sure you’re importing these things into a file that only displays in the editor. There’s no purpose or benefit to loading this on the frontend and it will cause additional scripts to appear on the frontend for no reason which will cause your site to be slower.
Once you confirm your entry file is being built, the next step is make sure it loads in the block editor.
Adding your script to the block editor
Now that you have your entry file ready, you’ll need to enqueue it to appear in the block editor. There is a specific hook that you need to use for this purpose: enqueue_block_editor_assets.
This is not the same hook you’d use if you were including styles or scripts meant to appear inside the editor iframe. This hook is used to enqueue assets for the editor UI itself (such as the block toolbar).
Here’s a simple example of what this enqueue would look like:
<?php
add_action( 'enqueue_block_editor_assets', function() {
// Get the core-block-overrides.asset.php file generated by webpack.
$asset_info = get_asset_info('core-block-overrides');
wp_enqueue_script(
'core-block-overrides',
get_theme_file_uri( 'src/js/core-block-overrides.js' ),
$asset_info['dependencies'],
$asset_info['version'],
true
);
} );PHPget_asset_info
This function is a custom one I created for my theme to dynamically fetch the version and script dependencies that a given entry may need to run. This is necessary to ensure the various @wordpress/* packages you’re importing or other scripts included by WordPress are properly enqueued along with your file.
If you’re not familiar with this, check out the relevant section in the post I wrote recently.
Once you’ve added the necessary PHP to your plugin or theme, check out the editor UI and you should see your new block format in the toolbar!
Conclusion
Now you know how to add a block text format you can easily add new ones for things besides gradient text. As long as you can style it with CSS and a single HTML element, it be should straight forward from here to add any other formats you wish by creating a new file in the same folder as the gradient-text.js file and then importing it into that index.js file you created.
Happy coding!
References
- How to easily set up Webpack for a WordPress theme with 10up-toolkit
- Formatting Toolbar API
- 4 new habits for adding stylesheets and scripts to WordPress
- JavaScript Object Destructuring
- CSS background-clip
- enqueue_block_editor_assets
- When you’re doing professional WordPress work it’s advisable to make everything translatable by default. This makes it much easier to support translations in the future and doesn’t require much effort up front. ↩︎