Writing effective CSS for WordPress

WordPress is undoubtedly a great content management platform and for the many use cases it covers it brings a lot of developer benefits on the table. Among the benefits, however, there are a handful of thorns sticking out and one of the sharpest ones is arguably CSS authoring which unfortunately can’t be blunted by the core team because it’s not inherently a fault within WordPress itself.

After authoring more than a hundred massively used themes and a number of fairly popular plugins, the struggles of writing CSS for WordPress are constantly evident. This piece will attempt to identify specific problems we face as design engineers, bad practices and code smells to look out for (some of which myself very guilty of), and some thoughts and patterns on how we can improve the landscape as a community.

CSS is nuts

While CSS looks simple and fairly straightforward, anyone who embarks on authoring styles for a medium to large product with a handful of intertwined components will quickly realize that it’s a rather complicated beast to tame. One piece of evidence supporting the previous claim is merely the large amount of proposed patterns and methodologies on how to organize styles (from mnemonics like BEM to writing CSS in JavaScript).

Pete Hunt (ex Instagram/Facebook) once claimed that “CSS is write-only“, a statement that although was said in a humorous context it breathes a lot of truth: for large scale projects there’s a very real danger that deleting or modifying seemingly unnecessary lines of CSS can have harmful effects on other parts of your application without realizing it.

Reflecting on WordPress

This particular problem is quite clearly evident in WordPress; the global nature of CSS in combination with the vastness of the WordPress ecosystem further exacerbate the troubles of writing modular, correct and encapsulated stylesheets.

WordPress is beyond a “large scale project”, it’s quite vast, and by that I don’t mean the core codebase. There are literally thousands upon thousands of themes and plugins out in the wild and for theme or plugin authors this means that every single rule-set you’ve ever written will compete at some point with a multitude of other ones. For website developers (people who actually use themes and plugins) it means that trying to style a website into their client’s expectations becomes a humongous ordeal riddled with impossible to override nested rules while swimming in the depths of specificity hell.

In a perfect world: Plugins and themes keep their stylesheets encapsulated and as clean as possible. A plugin can never interfere with the theme’s styles, and themes don’t make the task of writing styles for a plugin harder than it should be.

What we currently have: A vicious circle where plugin authors have to write extremely inefficient CSS selectors in order to overcome badly written theme styles, and theme authors that try to override global rules that plugins introduce in their stylesheets.

As we mentioned earlier, the problem is not something easily addressable. Mitigating it will have to be a community effort and very specific steps should be taken: Deeply understand CSS, acknowledge how our actions might affect the ecosystem, educate ourselves on some of the better practices, and finally examine how CSS is used right now out in the wilderness. Following we’ll cover some of the above.

Taming the cascade

CSS stands for Cascading Style Sheets and as the excellent MDN article on the cascade and CSS inheritance mentions this should give us a hint to how important it actually is. So, what is it then? The cascade, put simply, is the algorithm which decides how styles from multiple sources should be combined into a final rule-set which will be applied on the targeted document element.

There are countless of online resources available which anyone can freely use to get a better understanding of the subject but it’s important to go over a quick refresher. For brevity’s sake we’ll skip a few parts of complexity (like animations, media queries, etc) and focus on the three most important ones listed by weight descending (meaning the earlier ones will override the latter):

  1. Importance
  2. Specificity
  3. Order

Importance, in short, is the handy but dangerous !important keyword. Anything containing this part of CSS syntax at its tail will take precedence over any other declaration on the same element, regardless of specificity or order. Generally speaking (rare exceptions aside), !important should be avoided at all costs. Every time you’re writing an !important declaration chances are you’re making another developer’s life harder.

Specificity roughly refers to how many elements could be matched by a given selector. MDN gives the following definition:

Specificity is basically a measure of how specific a selector is — how many elements it could match. As shown in the example seen above, element selectors have low specificity. Class selectors have a higher specificity, so will win against element selectors. ID selectors have an even higher specificity, so will win against class selectors. The only way to win against an ID selector is to use !important.

Note that !important has no effect on specificity, it simply supersedes it.

Specificity is measured in arbitrary units and follows these simple rules:

  1. If a declaration is inside a <style> element (i.e. inline in the HTML) or a style HTML attribute (i.e. inline on the element itself) add 1000.
  2. For each id selector add 100.
  3. For each selector that is not an id or an element (i.e. classes, attributes, pseudo-classes etc) add 10.
  4. For each element or pseudo-element selector add 1.

Use this handy online specificity calculator to get a better idea. For a more in-depth analysis the MDN article is a highly recommended read.

Each CSS selector then has its specificity calculated and the final styles are extracted from rules with the highest number (highest specificity). This explains why inline styles or styles applied within the HTML document itself within a <style> tag tend to override anything else.

Οrder of declaration, is one of those things usually learned first when beginning with CSS and probably the easiest to grasp: Given equality in importance and specificity, rule-sets declared later in a stylesheet are prioritized. This is something that we’ll also address later on as we have full power of when to load styles as theme or plugin authors.

As mentioned previously, the intricacies of how the cascade works is something that every design engineer should be aware of. For a more in-depth analysis the following two articles from MDN (Cascade and inheritance, Cascade – CSS) are the absolute minimum as starting points.

Now that we’ve got a quick refresher on the basics it’s time to actually figure out how to make our lives easier.

General guidelines

Never use !important unless you absolutely, positively, 100% know what you’re doing. In case I wasn’t clear before.

With that out of the way, when authoring stylesheets for any kind of product it’s important to keep specificity as low as possible. This is a very basic rule that you’ve probably heard time and again, but it’s worth repeating. It makes overriding styles much easier when necessary (e.g. for theming purposes). A battle tested CSS methodology like BEM or SMACSS will not only help you write better, more maintainable stylesheets but also keep specificity low by enforcing class-only selectors.

Most CSS methodologies also strongly discourage nesting selectors. While this is not always possible for WordPress’s default markup, it is sound advice and should generally be considered the best practice for any custom theme markup, except for the case of plugin styles where as we’ll see later it’s important to scope everything.

By having the above basic guidelines in mind we’re already on a better path.

Styling themes

Everything in WordPress starts with a theme. The theme is where everything comes together, and as theme authors of the most popular content management system to date it’s imperative to always keep in mind that we’re not developing in a sandbox. Our themes will eventually be used or worked on by many people. It’s important that theme stylesheets are as little invasive as possible.

Start simple, provide sensible defaults

Authoring CSS for a WordPress theme doesn’t have to be all that different from, say, a static website, or any other product. With the exception of WordPress’ default markup (that we do have to take into account at some point) all the good old patterns still apply.

Start with a battle tested style normalizer (like normalize.css) and provide a css reset if necessary (a great resource is Bootstrap’s reset which is heavily commented and up to par with modern standards).

We can then continue styling one by one all HTML elements. Every theme should provide default styles that match our design for every element, especially the most important ones (form inputs, buttons, lists, tables, etc).

It’s a good idea to keep those styles on element selectors. It keeps specificity as low as possible (so that they’re easily overridable if required) and makes sure that any third-party content that happens to output unstyled (or understyled) markup will match our theme’s design. Here’s how this would look like (in compact form for brevity):

/* Top of our style.css file after resets */

/* General Typography */
body { font-family: 'Open Sans', sans-serif; }
h1, h2, h3, h4, h5, h6 { font-weight: 700; margin: 0 0 30px; }
h1 { font-size: 36px; }
...

/* Form Styles */
input[type="text"],
input[type="email"],
input[type="url"],
input[type="password"],
input[type="search"],
input[type="number"],
input[type="tel"],
input[type="range"],
input[type="date"],
input[type="month"],
input[type="week"],
input[type="time"],
input[type="datetime"],
input[type="datetime-local"],
input[type="color"],
textarea { }
...

/* Buttons */
button,
input[type="submit"],
input[type="button"] { }
...

/* Tables */
table { }
th { }
td { }

/* etc.. */

Note that the above are not resets. We’re at the point of actually styling our theme, with our design’s layout, sizes, colors, etc.

Similarly, our widgetized areas and widgets should also have baseline styles. In this case it’s good to have consistent widget classes for all sidebars and provide them with sensible theme defaults:

/*
* Providing easily overrideable baseline styles
* for consistent widgetized areas
*/
.widget {
  margin-bottom: 30px;
  font-size: 14px;
}

.widget-title {
  margin-bottom: 20px;
  font-size: 24px;
}

Individually styling each widget’s element should be namespaced, as we’ll see later on.

Treat your content areas as holy sites

It’s a common practice (and request) to have a few different styles on areas where the_content or widgets are outputted. Perhaps some fancy link underlines, maybe a couple of dropcaps or larger font size than the rest of the website. We should be very wary when doing this; these are the more common places every plugin and shortcode also outputs their content. Our goal here is to avoid adding overly specific styles which would result in conflicts with various plugins or shortcodes by other third parties.

One way to mitigate this issue is to write our base styles with our content’s design in mind, and reset/modify them wherever they’re not necessary. For example:

/* 
* These are styles our content areas will have anyway 
* and are easily override-able because of their absolute minimum specificity
*/
body {
  font-size: 16px;
  line-height: 1.65;
}

a {
  color: #f00;
  border-bottom: 1px solid #f00;
}

/* 
* Override them wherever they're not needed 
* (we're nesting here merely to illustrate the point)
*/
.navigation a {
  color: #ff0;
  border: 0;
}

Unfortunately this technique has its drawbacks, most prominently it can be a very tedious process to constantly have to override rules. If this becomes a problem the recommended approach would be to namespace all content styles under a single, popular, class selector: .entry-content.

Simply wrap your main content inside a div class-named .entry-content and style accordingly (but keep your selectors at most two levels deep!). It’s important to note that .entry-content is not randomly chosen. It’s based on the microformats specification and denotes our.. well.. main entry content. It’s also used by most of the popular themes (including the official WordPress ones) which makes it a perfect target for any plugin to have in mind when resetting its own styles.

Keep widget styles clean and basic

Treat widgetized areas in ways similar to the_content. Many plugins come with their own widgets (which most times have their own styles) or output content in widgetized sidebars.

Styling both WordPress’s default widgets and our own custom theme ones are of the few exceptions where we should be a bit more specific in selectors.

Don’t do this:

/* 
* We avoid writing overly general selectors
* for areas where third-party content can appear 
*/
.widget li {
  border-bottom: 1px solid #f00;
}

A better approach is to target each widget individually:

.widget_meta li,
.widget_pages li,
.widget_categories li,
.widget_archive li,
.widget_nav_menu li,
.widget_recent_entries li {
  border-bottom: 1px solid #f00;
} 

This avoids conflicting issues with any third-party widgets that may be added on our theme at a later point.

If you find yourself getting tired of repeating parts of many selectors like above, consider using a CSS pre-processor like Sass which will help remove most of the tediousness by nesting them.

Do not force priority on your theme’s main stylesheet

Stylesheets should be allowed to load in their natural order, keeping complexity at a minimum. If our main stylesheet relies on third party or vendor styles, we simply declare them in wp_enqueue_style as dependencies. Ultimately, our theme should enqueue one and only one stylesheet (the main style.css); everything else should be registered as a dependency.

Styling plugins

While theme authors start on a blank canvas, authoring styles for a plugin is a different story. In this case our canvas is the host theme itself and our plugin’s styles will most likely compete with those of the host. It’s important for a plugin to have two qualities: Never interfere with a theme’s styles and be consistent in styling among themes.

Namespace every class and scope every single selector

This is another exception for the nesting rule we covered previously. A plugin should have all of its styling rules nested under a single class (preferably something unique, like the plugin’s name) and, in addition, all the plugin’s classes should be namespaced. This is the only way to make sure our plugin’s rules will never interfere with the host theme.

Don’t do this:

/* This is a very common class name and we just overrode it globally */
.button {
  border: 2px solid #f00;
}

/*
* Although this could generally be OK we'll often
* find that it's not enough to overcome
* theme styles which are more specific
*/
.my-plugin-button {
  border: 2px solid #f00;
}

A better way to make sure that we won’t override anything important and at the same time have a higher chance to override the theme’s specificity on sandboxed elements is the following:

.my-plugin .my-plugin-button {
  border: 1px solid #f00;
}

What if our plugin’s name is very long? First of all no-one died due to long CSS class names. If it’s really that long and we can’t help it, we can abbreviate. For example Contact Form 7 uses .wpcf7 as its namespace and scope parent.

Again, a CSS preprocessor can be very handy here, we can nest everything under our parent class just once and continue writing CSS as usual:

/* Using Sass */
$namespace: 'my-plugin';

.#{$namespace} {
  .#{$namespace}-button {
    border: 1px solid #f00;
  }
  
  .#{$namespace}-content {
    font-size: 16px;
  }
}

Note that this doesn’t mean we should go overboard with nesting. After the first level, it’s back to best practices. Avoid it any futher!

Provide style resets for all your scoped elements

For plugins we have to be a bit more verbose and plan ahead. Most times it’s not sufficient to add a background color on a button and call it a day. Many themes will have all kinds of styles applied on their buttons and if we don’t reset them we’ll most certainly end up with little button frankensteins.

/* This is not enough */
.my-plugin .my-plugin-button {
  background-color: #f00;
  color: #fff;
}

/* It's important to reset most of our plugin elements */
.my-plugin .my-plugin-button {
  display: inline-block;
  font-weight: normal;
  border: 0;
  border-radius: 0;
  box-shadow: none;
  width: auto;
  height: auto;
  background: none;
	
  background-color: #f00;
  color: #fff;
}

Reset your elements targeting .entry-content

For simple elements that are usually scoped under .entry-content in themes (like tables, anchors, and lists) consider applying an extra safety reset, e.g. .entry-content .my-plugin-link { }. Warning: only for aesthetic declarations, no layout!

Our selectors are set in stone

Think long and hard about your selectors. Once a plugin is published changing them can cause a lot of trouble. Other people might be basing their theme support on our current styles and it can be a pain to update every theme without breaking anything.

The only way to change selectors at a later point without making people angry would be to add the new ones on top of the old ones (a practice that WooCommerce follows, for example), which will make your code less maintainable and quite a bit more confusing.

Provide a setting to turn styles off & separate stylesheets into domains

Depending on our plugin’s audience (e.g. this is very common for plugin-frameworks like WooCommerce), it might be a good idea to give the option to unset styles, in case someone would like to provide their own. Check out how WooCommerce does it for example.

Another good idea for larger, framework-like plugins is to separate stylesheets into domains, i.e. have different stylesheets for layout, mediaqueries, and aesthetics (colors, etc). That way it’s much easier for someone to provide a different custom theme to our plugin without having to re-style its layout.

Test your plugin against the top 100 themes of WordPress.org

The last piece of advice can be the most tedious but it has shown to be the most valuable in terms of figuring out how our plugin behaves in various popular themes and what we can do to fix it.

Here’s a small bash one-liner with WP-CLI that we use here at CSSIgniter to automate the installation of the first hundred most popular themes on WordPress.org for our own testing purposes:

curl -gs "https://api.wordpress.org/themes/info/1.1/?action=query_themes&request[browse]=popular&request[per_page]=100" | grep -o -E -e '"slug":".+?"' | awk -F: '{ print $2 }' | tr -d \" | xargs wp theme install

Simply install WP-CLI and run the above in your terminal while inside the directory containing your WordPress installation.

Beware though: avoid fixing very special edge cases caused by a theme’s bad usage of CSS, it only adds to the vicious circle! Instead, open a ticket on the theme’s support forum and try to work the issue with the theme author.

And that’s it! We’ve certainly covered the mere basics here but even so, they should be more than enough as a starting point for a much better developer experience.

Do you have any tricks or tips up your sleeve that help alleviate the pains of writing CSS for the WordPress ecosystem? Feel free to let us know by leaving a comment below.

Subscribe to our newsletter.

Get fresh WordPress content straight into your inbox. We hate spam more than you do.

1 comment

  1. Great write-up. It is very clear and useful. Thank you.

Leave a Reply

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