At some point nearly every WordPress site needs a few lines of custom CSS – the theme’s “spacing medium” is too tight, a plugin’s button is the wrong shade of orange, a specific page has an element you want to hide, or a widget’s typography does not match the rest of the site. The customization panels built into modern themes cover a lot, but they never cover everything, and the gap between “almost right” and “actually right” is usually a CSS rule or two away.
This guide covers the four places CSS can live in a WordPress site, when to use each one, how to write rules that actually override the theme’s defaults without resorting to
!important
, and how to test CSS in the browser before committing it to your site. Get these four things right and CSS on WordPress becomes a solved problem rather than a running source of frustration.
The four places CSS can live#
- Additional CSS (Customizer) for classic themes. Small rules, stored in the database, persists across theme updates.
- Additional CSS (Styles panel) for block themes. Same idea, different location.
- Child theme stylesheet for larger customizations. File-based, versioned, properly enqueued.
- Page builder CSS (Elementor, Divi, Beaver Builder) for builder-specific overrides.
The question is never “can I add CSS?” (you always can) but “which of these is right for the specific change I am making?” Pick correctly and your customizations survive theme updates, plugin updates, and handoffs to other developers. Pick incorrectly and you either lose the customization the next time the theme updates, or you end up with three layers of competing CSS none of which anyone can untangle a year later.
Method 1: Additional CSS in the Customizer (classic themes)#
For a handful of small tweaks – change a color, adjust a margin, hide a particular element – the Customizer’s Additional CSS panel is the correct choice. It is database-stored, meaning it survives theme updates. It is scoped to the currently active theme, meaning it does not leak into other themes if you switch. And it loads automatically on every page without needing any PHP or enqueue calls.
To add CSS:
- In the WordPress admin, go to Appearance > Customize
- Scroll to Additional CSS (usually the last item in the menu)
- Type or paste your CSS in the editor
- The preview on the right updates in real-time so you can see the effect
- Click Publish
The CSS is now live on every page of your site. To edit it later, return to the same panel – your previous rules are still there.
What Additional CSS is good for.
- Color changes the theme does not expose in its own settings
- Small layout tweaks (margins, padding, alignment)
- Hiding specific elements (
.announcement-bar { display: none; }) - Responsive breakpoint adjustments
- Overriding plugin styles that clash with the theme
What Additional CSS is not good for.
- Large refactors spanning hundreds of lines – use a child theme instead
- CSS with
@font-facerules (the paths get tricky; put these in the theme) - Anything that needs to load before the theme stylesheet (Additional CSS always loads after, which is usually what you want but is wrong for a few edge cases)
- Customizations you want to version-control alongside your other theme assets
The Customizer editor does basic syntax highlighting and tells you about obvious errors like unclosed braces. It is not a full CSS IDE – for anything substantial, write in your editor of choice and paste in.
Method 2: Additional CSS in the Styles panel (block themes)#
Block themes (Twenty Twenty-Four, Twenty Twenty-Five, any theme using the Full Site Editor) do not use the Customizer. CSS goes through the Site Editor’s Styles panel instead.
- Go to Appearance > Editor
- Click Styles (top right)
- Click the three-dot menu in the Styles panel
- Select Additional CSS
- Enter your CSS and click Save
Same idea as the classic theme Customizer – database-stored, survives updates, loads on every page. The only difference is where the panel lives. If you are switching between classic and block themes, this is one of the first UI changes you notice.
Some block themes (including the default Twenty Twenty-Five) also let you click directly on any block in the editor and use the Styles > Advanced > Additional CSS panel to add CSS that applies only when that specific block is in use. This is more surgical than global Additional CSS but scoped to the block’s presence in your templates.
The theme change and customization guide has the broader picture of how these panels fit into the block-theme workflow, including Styles presets and global typography which may replace the need for some CSS entirely.
Method 3: Child theme stylesheets (for larger customizations)#
When the custom CSS reaches 100+ lines, when you want to version-control it alongside other theme assets, or when it includes things a CSS-only panel cannot handle (
@font-face
declarations, CSS custom properties you want defined globally, CSS imported from build tools), a child theme is the right answer.
A child theme is a small theme that inherits everything from a parent theme and adds or overrides specific parts. In 2026 this is still the standard way to customize any third-party theme without losing the customizations when the theme updates.
Creating a child theme
A minimal child theme needs just two files:
wp-content/themes/
├── your-parent-theme/
└── your-parent-theme-child/
├── style.css
└── functions.php
The
style.css
starts with a theme header comment that declares the parent:
/*
Theme Name: Your Parent Theme Child
Template: your-parent-theme
Version: 1.0
*/
/* Your custom CSS goes here */
.site-header {
background-color: #002447;
}
The
Template:
line must match the parent theme’s folder name exactly. Without it, WordPress does not recognize the relationship.
The
functions.php
enqueues the parent theme’s stylesheet plus your child’s, in the right order:
<?php
function child_theme_enqueue_styles() {
wp_enqueue_style('parent-style', get_template_directory_uri() . '/style.css');
wp_enqueue_style(
'child-style',
get_stylesheet_uri(),
['parent-style'],
wp_get_theme()->get('Version')
);
}
add_action('wp_enqueue_scripts', 'child_theme_enqueue_styles');
The
['parent-style']
dependency ensures the parent stylesheet loads first, so your child rules override correctly. Zip up the folder and upload it through Appearance > Themes > Add New > Upload Theme, then activate the child theme. All your parent’s styles still apply, plus whatever you add to the child’s
style.css
.
Full walkthroughs for child themes (and when to build a theme from scratch instead) are in how to change, install, and customize a WordPress theme and the deeper how to create a custom WordPress theme from scratch.
When to use a child theme over Additional CSS
- The CSS is more than ~100 lines (Additional CSS becomes painful to navigate past that)
- The CSS needs to be version-controlled alongside other theme changes
- The customization includes PHP template overrides, not just CSS
- The CSS imports external files or declares
@font-face - You want the CSS to survive a WordPress export/import
- Multiple people work on the site and you need the customizations in git
Additional CSS is perfect for one-off tweaks. A child theme is the right tool once the customizations become substantial enough to maintain as their own thing.
Method 4: Page builder CSS panels#
If your site uses Elementor, Divi, Beaver Builder, or Bricks, each of them has its own CSS panel – often per-page and per-element in addition to site-wide.
Elementor: Each widget has a Custom CSS section in its Advanced tab (requires Elementor Pro). Per-page CSS in Page Settings > Advanced > Custom CSS. Global CSS in Site Settings > Custom CSS.
Divi: Each module has a Custom CSS section in its Advanced tab. Per-page in the builder’s Page Settings. Global in Divi > Theme Options > General > Custom CSS or Divi > Theme Customizer > Additional CSS.
Beaver Builder: Global CSS in Settings > Beaver Builder > Tools > Custom CSS. Per-row, per-module CSS in each element’s Advanced tab.
Page builder CSS is useful when the override is scoped to one page or one element and you want the CSS to live alongside the page content rather than with the theme. It is not useful for site-wide changes – those should go in Additional CSS or the child theme, so you only have one place to look when debugging.
The one gotcha: page builder CSS does not survive disabling the builder. If you uninstall Elementor and the custom CSS was in Elementor’s Custom CSS panel, that CSS is gone. For anything you want to persist regardless of the builder, put it in a child theme or Additional CSS.
Test CSS in the browser before committing it#
The biggest mistake people make with custom CSS is writing rules in the WordPress panel, publishing, checking the site, finding it is still wrong, editing again, and repeating. The fast path is to write the CSS in your browser’s dev tools, confirm it works, and then copy the working rule into WordPress.
The workflow
- Open your site in Chrome, Firefox, or Safari
- Right-click the element you want to change and select Inspect (or press F12)
- The dev tools open with that element highlighted in the HTML panel
- In the Styles panel on the right, you can see every CSS rule affecting the element, in order of specificity
- Click inside the Styles panel to add a new rule – type the property and value as you would in a stylesheet
- The page updates in real-time as you type
- Once you have a rule that works, copy it from the dev tools into WordPress’s Additional CSS panel
This catches three common problems before they become frustrations:
- Selector specificity. Dev tools show you every rule that is applying, including which ones are being overridden. If your new rule does not work, you can see exactly which other rule is beating it.
- Typos. Invalid property names or values fail silently in production – in dev tools they show a yellow warning icon immediately.
- Cascade effects. You might change
.buttonand discover it breaks the pagination buttons on the archive page. Dev tools let you test every case before shipping the rule.
When the rule works in dev tools, copy the entire rule (selector + declarations) and paste into Additional CSS. Save.
Finding the right selector
Right-click the element in the dev tools HTML panel and choose Copy > Copy selector. Some dev tools also have Copy > Copy full XPath or Copy > Copy CSS path. For CSS work, you want the CSS path.
A copied selector like
body.page-id-42 main.site-main article.post-42 > div.entry-content > p:nth-of-type(3)
is too specific – it targets only that one paragraph. Pare it down to the smallest selector that still uniquely identifies what you want:
.entry-content p:nth-of-type(3)
or just
.entry-content p:first-child
if “the first paragraph in any post body” is what you actually mean.
Common CSS tasks on a WordPress site#
Change a font or font size
body {
font-family: 'Inter', sans-serif;
font-size: 17px;
line-height: 1.6;
}
For custom fonts, see how to add custom fonts to WordPress – the font has to be loaded before it can be applied, which is a separate concern.
Adjust spacing
.site-header {
padding: 24px 0;
}
article h2 {
margin-top: 48px;
margin-bottom: 16px;
}
Hide an element
.announcement-bar {
display: none;
}
/* Hide on mobile only */
@media (max-width: 768px) {
.sidebar {
display: none;
}
}
display: none
removes the element entirely (no layout space).
visibility: hidden
keeps the space but makes the element invisible. Use
display: none
unless you specifically need the spacing preserved.
Change a button’s color
.wp-block-button__link,
button.primary,
input[type="submit"] {
background-color: #002447;
color: #ffffff;
}
.wp-block-button__link:hover,
button.primary:hover,
input[type="submit"]:hover {
background-color: #001a36;
}
Multiple selectors separated by commas apply the same rules to each. This pattern catches the different button classes WordPress and plugins use.
Responsive breakpoints
/* Base styles (mobile-first) */
.container {
padding: 16px;
}
/* Tablets and up */
@media (min-width: 768px) {
.container {
padding: 32px;
}
}
/* Desktops and up */
@media (min-width: 1200px) {
.container {
max-width: 1140px;
margin: 0 auto;
padding: 48px;
}
}
Use
min-width
for mobile-first (base styles apply everywhere, overrides kick in at larger sizes). Use
max-width
if you specifically want a rule to only apply below a certain size.
Styling specific pages only
WordPress adds a body class for every page, post, category, and archive. You can target them specifically:
/* Only on the page with ID 42 */
.page-id-42 .entry-title {
display: none;
}
/* Only on posts in the "tutorials" category */
.category-tutorials article {
border-left: 4px solid #ea580c;
padding-left: 24px;
}
/* Only on the home page */
.home .site-header {
background-image: url('/wp-content/uploads/hero.jpg');
}
View the page source (or use dev tools) to find the exact body classes for a page you want to target.
Understanding specificity (why !important is usually wrong)#
The question that causes most CSS frustration on WordPress sites: “I added a rule and nothing changed. What happened?”
The answer, 95% of the time, is that another rule with higher specificity is overriding yours. Specificity is CSS’s way of deciding which rule wins when two rules target the same element. Higher specificity beats lower specificity. Same specificity? The rule that comes later in the cascade wins.
Specificity is calculated from the selector:
- Inline style (
style="..."attribute) – highest - ID selectors (
#header) – high - Class selectors (
.button), attribute selectors ([type="submit"]), pseudo-classes (:hover) – medium - Element selectors (
p,div,button) – low - Universal selector (
*) – lowest
A selector’s specificity is roughly the count at each level.
.entry-content p
has one class and one element = medium-low specificity.
body.page-id-42 article.post .entry-content p
has two classes and three elements = higher specificity, so it wins.
When your rule does not work, open dev tools and look at the Styles panel. The rule that is winning has a line-through showing your rule was overridden. Look at the selector of the winning rule. Your rule needs equal or higher specificity to beat it.
How to beat an existing rule properly
Match the specificity. If the existing rule is
.entry-content p
, write your rule with the same specificity:
.entry-content p { ... }
. Since your rule comes later in the cascade (Additional CSS loads after the theme), it wins.
Bump the specificity by one. If matching is not possible, add one more selector level:
.entry-content p { ... }
becomes
article .entry-content p { ... }
. One extra element selector, enough to win.
Use a more specific existing class. Often the theme has given the element multiple classes (
<p class="entry-text lead-paragraph">
). Pick the more specific one:
.lead-paragraph
is more targeted than
.entry-content p
.
As a last resort, add
:where(...)
to lower the existing rule’s specificity. This is advanced, but modern CSS lets you write
:where(.entry-content) p { ... }
which reduces the specificity of the
.entry-content
portion to zero. Useful when you need to lower an existing rule rather than raise yours.
Why
!important
is usually the wrong fix
!important
overrides everything regardless of specificity. It feels like the magic wand that makes any rule work. It is not.
Problems with
!important
:
- It cascades. Once you add one, you eventually have to add more to override that one, and then more to override those. Many WordPress sites end up with CSS where every rule has
!important– which means!importantno longer prioritizes anything because everything has it. - It makes debugging harder. When two
!importantrules conflict, the later one wins – but you now have to track which one is later across all your stylesheets. - It signals the underlying problem was not understood. Every
!importanthides a specificity issue you could have fixed properly.
The rare case where
!important
is correct: overriding inline styles (which you cannot beat with selectors), overriding a plugin’s
!important
(you are escalating a war already started), or in user-agent overrides where you deliberately want a rule to win everywhere.
If you find yourself reaching for
!important
, open dev tools and look at why your rule is losing. Usually the right answer is to make your selector a bit more specific, not to force the issue.
Common mistakes#
- Editing the theme’s
style.cssdirectly. A theme update overwrites the file and every customization is gone. Use Additional CSS for small changes, a child theme for larger ones. - Putting CSS in
functions.phpviawp_add_inline_style. Works, but for visual CSS this is strictly worse than putting it in Additional CSS or a child theme – harder to maintain, harder for anyone else to find. - Not testing at mobile sizes. Dev tools have a device toolbar (Ctrl+Shift+M in Chrome/Edge, Cmd+Option+M in Safari). Always check at 375px and 768px before committing.
- Forgetting to clear caches. If the site uses a page caching plugin, FastCGI cache, or a CDN, new Additional CSS may not appear until the relevant cache is cleared. If your edits are not showing, try a hard refresh (Ctrl+Shift+R) or purge the cache.
- Inlining CSS via the HTML editor. WordPress’s classic editor lets you paste
<style>tags into post content. This works, but it scopes the CSS to that one post’s rendered HTML – it does not affect anything else, and it is a maintenance nightmare. Put the CSS in Additional CSS with a page-specific body class selector instead. - Putting
!importanton every rule as a reflex. You are building up CSS debt. Beat rules through specificity; use!importantonly as a last resort. - Using auto-generated selectors from dev tools without pruning. The “copy selector” feature often produces a selector that is unique to one element at one moment in time. A month later, the DOM has shifted slightly and your rule does not match anymore.
- Ignoring the cascade order. Plugin stylesheets usually load before the theme’s
style.css, which loads before Additional CSS. If your rule in Additional CSS is not winning, it is not because Additional CSS loads too late – it almost always loads last. - Forgetting about block styles. Block themes let you add custom CSS per-block via the Styles > Advanced > Additional CSS panel on individual blocks. If your site-wide Additional CSS is not targeting a block, check whether the block has its own CSS that is overriding you.
How Hostney handles custom CSS#
Hostney does not do anything special with custom CSS – whether it lives in Additional CSS, a child theme, or a page builder panel, it is ultimately just text that WordPress or the builder outputs into the page. The FastCGI caching layer caches the rendered HTML (including inlined Customizer CSS) per URL, so custom rules added via Appearance > Customize > Additional CSS get cached along with the rest of the page. Static stylesheets from child themes are served with the same long cache headers as any other asset in
/wp-content/themes/
, so returning visitors do not re-download them.
One detail worth knowing: the Customizer stores Additional CSS in
wp_options
under the
custom_css_post_id
reference, which means it is included in any standard WordPress database backup. If you migrate the site, import the database, and re-activate the same theme, the Customizer CSS comes along automatically. Child theme stylesheets are under
/wp-content/themes/
and move with the file-system backup. Both approaches survive migration cleanly – which is part of why they are preferred over editing the parent theme’s
style.css
directly.
For performance, if your child theme is adding several hundred kilobytes of CSS, the same optimization principles from the WordPress performance guide apply: minify, concatenate where it makes sense, and use the built-in
wp_enqueue_style
dependencies rather than stacking separate file requests. Most modern caching plugins (WP Rocket, LiteSpeed Cache, Hostney Cache) handle CSS minification and combining automatically.
Summary#
CSS on WordPress lives in four places, and picking the right one for the change you are making is most of the battle: Additional CSS in the Customizer (or Styles panel on block themes) for small site-wide tweaks, a child theme stylesheet for anything substantial, page builder CSS panels for overrides scoped to specific builder content, and – only if you cannot avoid it – direct edits to a from-scratch theme’s own
style.css
. Never edit a third-party parent theme’s files directly – updates erase everything. Test rules in browser dev tools before committing them to WordPress; the Inspect panel shows specificity, existing rules, and real-time previews, which makes the workflow dramatically faster than the save-refresh-check cycle. Beat existing rules through equal-or-greater specificity rather than
!important
– the latter compounds into unmaintainable CSS within a few months. Clear caches after changes, test at mobile sizes, and use body classes (
.page-id-42
,
.home
,
.category-tutorials
) when you want a rule to apply to one specific context rather than everywhere.