Add Custom Styles to Your WordPress Visual Editor

Part of creating a custom WordPress theme for a client involves designing custom styles, not only for the overall site, but also for specific needs or use cases a client might have. One way I can be most helpful to a client is to make sure that what they see as they work in the Visual Editor (also called TinyMCE) is what they want to see on their site. As I create a custom theme for a client, then, it’s really important for me to know what unique approaches my client wants for everything from subtitles to titles to block quotes, so that I can set up the theme appropriately. A client may want all titles to be in all uppercase letters, but it’s important to keep that as a purely visual option. Titles should always be properly capitalized: “My Cool Post About All Things” will adapt easily to future theme and design changes; “MY COOL POST ABOUT ALL THINGS” just won’t.

One tip I give my clients is to treat writing WordPress content similarly to creating a document in Word (or whatever document creation tool you prefer). Although it’s possible to go through your content and make subtitles stand out, for example, by making each one bold and big and a different color, all by hand, you set yourself up for a lot of work, possibly inconsistent styles, and the pain of having to do it all over again by hand if you decide that you want to change how subtitles look–the font, color, size, etc. Word comes with a lot of these styles designed and ready to go. You can adjust them as you like in accordance with your own branding and personal preference. The win comes if you want to modify a style down the road–every instance of that style in use will automatically be updated, too!

WordPress can give us the same kind of flexibility, and consistency. Although it’s possible to change styles by hand, and make subtitles stand out, for example, by making each one bold and big and a different color, all by hand, if (and when) you decide you want to change your theme, you’re left with countless instances of inline, or hard coded, styles, which are no longer consistent with your new look. Mike Hemberger just published a great tutorial for developers who want to remove extra buttons from the Visual Editor.

WordPress does come with a quick basic style selector, which has most of the standard styles a user will need–primarily headings. (Note: please, never never never use Heading 1 in your post content: that is for your post/page title. Developers, it’s easy to remove that from the menu, too: check out Jo Waltham’s quick tutorial on it.)

But what if you’ve created custom styles for your clients, beyond what’s available in that dropdown? Historically, I’ve had to tell them to switch over to the text editor and add those styles by hand, which can be hazardous to everyone’s health. The text editor is a scary place for some users, and trying to tell them how to add “button” as a class to a link may be too much for them. Plus, as the capabilities of the visual editor expand, it’s tedious to switch back and forth.

So I decided to look for a way to bring my own custom styles into the Visual Editor for my clients. And me. My goal was to find something simple and useful, and not over the top. I don’t create a ton of custom styles (usually), but I’d love to make them easy to find. And now I’d like to share that with you.

Note: everything in here requires the use of PHP, so roll up your sleeves and prepare to edit some code. Make sure you back up your files, please don’t use the WordPress theme editor, and practice safe coding.

Add an Admin file to your theme

Everything we add needs to be in your theme, because this is totally theme specific. You can put everything in your functions.php file, but I like to put all of this admin related code into one file, because I’m easily distracted. Mine is creatively called admin.php, added to an /includes/ directory, and I include it from my functions.php with something like this:

add_action( 'genesis_setup', 'leaven_setup', 15 );
function leaven_setup() {
  include_once( get_stylesheet_directory() . '/includes/admin.php' );
}Code language: PHP (php)

If you’re not a Genesis Framework user, just change the genesis_setup hook to something less theme specific, maybe after_setup_theme, or tie it in with however you add other theme PHP files.

admin.php: Add an Editor Stylesheet to Your Theme

This is a one-line WordPress function, and just tells WordPress that your theme includes a style sheet which is specific to the editor:

add_editor_style( 'editor-style.css' );Code language: JavaScript (javascript)

This will get you one giant step closer to an editing experience which mimics the actual look and feel of your site. Your editor style sheet does not (and should not) include everything that’s in your style.css, just the content specific bits, like font families, how to style block quotes, and lists, and whatever custom styles you may have. If you use Sass, you can separate out the content specific bits into a separate .scss file and automatically compile it. Here’s my editor-style.css, which probably has bits and pieces it doesn’t necessarily need, but gives you an idea.

admin.php: Load Custom Fonts in the Admin

If you’re adding custom fonts to your site, you’ll want to make sure they’re available in the editor as well. This little snippet loads my Google fonts (Open Sans and Lora) and makes them available to the Visual Editor. If you’re using Typekit, Thomas Griffin has a nice tutorial on adding that to the WordPress Visual Editor.

// Add Google fonts to editor
add_action( 'after_setup_theme', 'leaven_editor_fonts' );
function leaven_editor_fonts() {
	$font_url = str_replace( ',', '%2C', '//,400,700|Lora:400italic,700italic' );
	add_editor_style( $font_url );
}Code language: PHP (php)

We’ve made a great start at this point. Here’s what mine looks like so far, compared to what it looked like initially:


admin.php: Add a New Style Select Menu to the Visual Editor

I found a handful of tutorials on this, but all a bit old, and including some unnecessary code, so I’m cleaning up a bit and sharing again. When a new user opens the Visual Editor for the first time, there is one line of buttons available. The last button, now called Toolbar Toggle, opens a second line of buttons, most of which aren’t needed by the average user, and some of which are dangerous (the pesky colors button, for example). You can add your new style selector to either row. I kind of like the first row because I know it will always show, but it may be more semantically correct to put it in the second. Here’s the code for both (just pick one):

// Adds the style dropdown to the top menu in the editor.
add_filter( 'mce_buttons', 'leaven_mce_editor_buttons' );
function leaven_mce_editor_buttons( $buttons ) {
	$buttons[] = 'styleselect';

	return $buttons;

// Adds the style dropdown to the second row of buttons in the editor.
//add_filter( 'mce_buttons_2', 'leaven_mce_editor_buttons_second' );
function leaven_mce_editor_buttons_second( $buttons ) {
	array_unshift( $buttons, 'styleselect' );

	return $buttons;
}Code language: PHP (php)

The first row code adds the style selector to the end of the first row; the other adds the selector to the beginning of the second row.

It’s really important not to stop here, because at this point, you’ve added a drop down with all kinds of generic styles in it, some of which are dangerous, some of which are already covered by your buttons. So now we get to the fun part.

admin.php: Add Custom Styles to Your Style Selector

Here’s the fun part. We’re going to replace the excessive style selector and add only our needed and custom styles to it. If they’re in the editor style sheet, we can use them and see the magic happen right there in the visual editor, which is pretty awesome. Here’s the code:

add_filter( 'tiny_mce_before_init', 'leaven_modify_mce_styles' );
function leaven_modify_mce_styles( $init_array ) {
	// Define the style_formats array
	$style_formats = array(
			'title'    => __( 'Button', 'leaven' ),
			'selector' => 'a',
			'classes'  => 'button',
			'title'   => __( 'Notice', 'leaven' ),
			'block'   => 'div',
			'classes' => 'notice',
			'title'    => __( 'Disclaimer', 'leaven' ),
			'selector' => 'p',
			'classes'  => 'disclaimer',
	$init_array['style_formats'] = wp_json_encode( $style_formats );

	return $init_array;
}Code language: PHP (php)

I’ve added three custom styles here, but sky’s the limit for you. I’ll explain my styles, but let me tell you the two absolutely required keys:

  1. title: this is the label for the dropdown item. Bonus points for making it a i18n ready.
  2. selector | block | inline: for an excellent explanation of these, check out this post from AlisotheGeek. Look for the table, which explains every key. Basically, you’re telling WordPress whether to apply the style to an existing element or to create a new element (inline or block).

Here’s a breakdown of my styles:

  • button: the selector is critical here. It tells WordPress to modify the existing selector (in this case, the link itself), rather than replace it. My buttons have a creative custom class called “button”. If you have multiple button styles (like a red button and a blue button), you can make a custom style for each, and add the class like “button red” or “button blue”, if that’s how the styles work for you.
  • notice: the important thing here is that because my notice is a block level element, I don’t use selector; instead, I use block. This tells WordPress to wrap the selected/highlighted text in a <div> element and then add my custom class (“notice”) to it.
  • disclaimer: here, I’m back to a selector, rather than block. This tells WordPress to add a custom class (“disclaimer”) to the selected paragraph. I could set it as a block/div instead if I wanted to, but it’s worth tinkering with and seeing what works for you.

At this point, you’re done. If your custom styles are in your editor style sheet, your visual editor will now show them (here are my three):

WordPress TinyMCE Editor Custom Styles in Action

Because all you’re doing is adding custom classes to specific elements, it’s much easier to make sure these styles carry over to your new theme, simply by making sure the new theme includes classes for buttons, notices, and disclaimers.

So that’s it–a fair bit of setup, but once you have the big pieces in place, adding new styles is pretty straightforward, and much easier for clients to manage as they write.

Reader Interactions


  1. Hans says

    Great subject and post, Robin, I was just looking for ways to make the edit-experience more complete. Can’t wait to try it out. Thanks, Hans

  2. Hans Schuijff says

    Hi Robin, I’ve tried it out now and had some problems getting your code to work. I’m using a genesis chid-theme (Digital Pro) and have used the debugger console to check if the functions where properly called.

    The first problem was with your first piece of code. I suspect the hook genesis_setup doesn’t exist, since it didn’t work and I have found no other reference to that hook in the genesis documentation or other blogs. You might wanna use another hook like ‘init’, like I read in another article on the subject. I first just put the require_once (without hook or function) directly in the functions.php, to be shure it was run, and that worked too.

    After solving that problem, the css was properly applied in the editor, but the format dropdown wasn’t added. I checked if the functions were called, and the were, so I didn’t know what to do. Only after deactivating the “TinyMCE Advanced” plugin it showed and the problem was solved.

    There was a third problem and that concerns the post from AlisotheGeek you point to. My antivirus program finds a trojan horse there and won’t allow access.

    Like I said, I was already on the subject of getting a more fitting visual editor experience and my first reference of it was an article of Bill Erickson about shortcodes. Perhaps you have crossed it too?

    He points to a post on wpbeginner that both shows how to add custom styles and guided me to a plugin called “TinyMCE and TinyMCE Advanced Professsional Formats and Styles”. That plugin enables adding custom styles too and offers a settings ui for adding styles from a wp-admin dialog. However, this plugin has not been updated for 2 years. Luckily someone made a forked version that is more current and called “TinyMCE Custom Styles”. I tried it, and after disabling tinymce advanced that worked perfectly too. For all non-coders that can be a neat sollution perhaps? Making editor css still needed, but the rest is then more automatic or guided by a dialog.

    On my path to this knowledge, I came across some other links in the articles, that perhaps are a nice addition to the great ones you have already shared here… I mentioned the shortcode-post of Bill Erickson and wpbeginners already, but Bill has a post on Adding Editor Styles is nice too. It describes a part of what you do here, and the wp-doc of the plugin “TinyMCE Custom Styles” points to the tynymce documentation that describes formatting, a nice addition for anyone who wants to get to the bottom of it, i think. The settings page (in wp-admin) of this plugin also shows a short description of the options, so that makes the table in the perhaps infected page redundant.

    Between all these great sources I have learned a lot. Thank you again Robin. Great work!

    • Robin says

      Thanks for sharing all of your research, Hans–much appreciated! Yes, if you are using a plugin or other code to modify the TinyMCE editor, this tutorial/code doesn’t account for it, so things may not play well together.

      Just a note–the genesis_setup hook is added in Genesis’ init.php file. It’s quite an early hook.

      • Hans Schuijff says

        Hi Robin,

        Just giving some back. My pleasure.

        Thanks for telling me where to find the genesis hook. I don’t know why the add_action didn’t work, I just know the leaven_setup function wasn’t running (according to a console-message I added to be sure), and so admin.php wasn’t included. For me, your code only started working after pulling the include_once instruction out of the function. Up to that point I had just copied all the code-examples given by you, and put them in the required files.

        Off course I don’t expect to use your code next to a plugin that does the same, it’s one or the other. I found the plugins only after I got your code running and wondered why the button wasn’t appearing. When the plugin also didn’t show the format-pulldown, I looked further and found the conflict with TinyMCE advanced. So in the end I got both your code and the plugin to run like expected, except for the hook.

        It will add some nice possibilities, so thanks for that. I never liked having to go to the text version to add classes like “button” or “Intro” to links and paragraphs and now don’t have to anymore. Such a small effort that I wonder why it’s not standard practice.

        Cheers, Hans

      • Hans Schuijff says

        Some addition occurred to me…

        I’ve read somewhere that it is good practice to only include scripts in pages where they are relevant, so each page can be as lean as possible. And that plugins that don’t target which pages to load their JavaScripts can cause compatibility and performance issues more easily.

        Thinking about that, made me wanna test if one is on an edit-page before inserting the code. I don’t know how to do that specifically, you might know more about that, but have at least thought to put an conditional if ( is_admin() ) around the include_once in the leaven_setup function.

        It’s not much code this time, but perhaps important to get the routine thinking about that sort of things too? Without conditional, the code is loaded on the frontend too.

        Well, for what it’s worth.

        • Robin says

          It’s a reasonable question. The hooks/functions in this tutorial are (mostly) admin related already, so I don’t know that the conditionals are strictly necessary. If you want to prevent the admin file from being included on the front end at all, I suppose that’s a fairly quick thing to implement, although I feel like the practice I normally see is to always include all the files, and make the necessary functions/methods behave conditionally. If a function is not using a specific enough hook, then yes, adding conditional checks into it would be really important.

          • Hans Schuijff says

            Yes I can see that too. And one could say that if code contains the proper selections there is little harm in loading more code than is used at any specific time. The only reason to not do so, is that there is always a chance of code conflicting or containing errors and that more code means adding to that chance.

            Also it means adding some to pageload and performance, since it means more code to load and interpret. I have read in the support of some populair plugins that they find it bad practice when plugins load their scripts on any page, so selection of what to load on what page is important to some developers. When loading frameworks and js-libraries that might be of more importance than the small amount of code that this example.

            I have bought some themes that where very bloated and loaded lots of code I didn’t need. Loading 0.5+ MB’s of code is a lot, and I didn’t like it. My interest is keeping my install as lean as possible, and I think the advise to load only what’s needed is a sound advice in many ways.

            Well, enough said. Thanks again for the learning and dialogue. Have a nice day, Robin.

  3. Ben says

    Hi Robin,
    Really useful stuff, thanks.
    I got this working for pretty much everything, but I don’t think it’s possible to apply custom TinyMCE formats to images with captions. Bit of a niche case I know, but it’s a dead-end I have been lost in for hours now!
    The problem is perfectly articulated in this WP support forum post – that never had any replies!

    It seems like creating functions to change the way that the WP TinyMCE editor natively handles images with captions, isn’t the best solution (if it’s even possible) or a very future-proof one? So I’m probably better off just teaching the clients to add in an image with no caption, apply the custom format (which adds the wrapper div), then go back and add the caption to the image. It’s a bit clunky but it does work.


  4. Hans Schuijff says

    I found out why the genesis_setup hook wouldn’t work for me. The first statement in the childtheme’s functions.php (like most Studiopress child theme’s) is an include_once of genesis init.php and that means the hook is fired before any other code in the functions.php. It means the code you offer must be put in a place before that include-statement to work. Good to know, and perhaps something to mention?

    • Robin says

      Ah, I had not realized that–I’ve long gone to a different approach of setting up my functions.php file which doesn’t require that, using a theme setup function. I pretty much always only use my own child theme at this point, so this is good to know.

      • Hans Schuijff says

        I just read the article of Bill Erickson on making a core functionality plugin and an article on how to structure functions.php better. This discussion and github fits right into that thinking. I will look more into it and probably change my ways too. Thanks for the tip. Learning a lot. 🙂

  5. Greg says

    Thanks for this tutorial. I’ve been searching for a while and never come across a method to add the custom format to the native selectbox rather than creating a new one. I know I can load the default headings and paragraph tags to my new Formats selectbox but it doesn’t show which format is selected when the select is closed. My screenshot below shows what I mean. The new Formats selectbox is inferior to the native one IMO. Is it not possible to insert the new custom formats into the native selectbox?

    • Jeppe says

      Exactly what I wan’t to. Can’t find any resources on how to do it.

  6. Chris Adams says

    Hey Robin, can I ask how you could use this to allow for the client to add columns to this editor… (one-half first, one-half, third, fourth etc)? At the moment I am using the admin.css to show these styles in the visual editor but I am short of providing a way for the user to easily format content into columns without them having to add and then inevitably it gets mucked up. The only other option (which is a good one but has time and cost implications) is using Advanced Custom Fields to generate this in a template…
    Thanks for your post.

    • Robin says

      Hey Chris! I’m sure it’s possible to do it, but it would be a big job. You might consider using Genesis Columns Advanced to handle the columns, or at least looking at how they’ve managed it–I think it’s adding shortcodes to the editor to handle the columns output, which is a different approach, but may still provide insight.

  7. Dina says

    I have two custom style formats – the first adds the class “style1” and the second adds the class “style2” – and the selectors for both are <a> elements. When I select the first style for my link, but then decide I want the second style instead, it simply adds the second class to the element, instead of replacing it. Based on the TinyMCE and WP documentation, I think the way to do this is to use theme_advanced_styles, but I can’t seem to get it to work. Do you have any thoughts on how to replace the classes instead of continuously appending them?

    • Robin says

      I would try changing selector to inline, maybe, and see if that does what you’re wanting, per the second note after the code example. Looks like the link is no longer available, unfortunately, but it seems like the selector|block|inline is the thing to maybe change. HTH

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.