Site icon AXJ NEW YORK GLOBAL NEWS NETWORK

Gutenberg Times: 14 ways to add Custom CSS in WordPress Block Editor

With the new Custom CSS on a block and post/page level, I was wondering if I would know where to look if something needs to be changed that was modified by a Custom CSS setting. Turns out there are 14 different ways to skin WordPress. I am a fan of keeping one source of truth, but it might not always be possible to keep it in theme.json. So I also wanted to figure out what decisions making process goes into selecting a method in the first place.

Below is a comprehensive reference for users, site builders, designers and developers covering all methods from no-code to full theme development.

These methods only apply to Block themes, though. Classic theme methods (like the Customizer’s Additional CSS panel etc.) are excluded.

The specificity cascade

Before diving in, it helps to understand WordPress’s style hierarchy for block themes. Styles resolve in this order, with later layers overriding earlier ones on four levels.

Within each layer, more specific targets (per block, per element) override more general ones. This cascade is central to how all the methods below interact.

1. theme.json — structured style properties

Who: Theme developers, child theme authors
Where: theme.json (root of your theme) → styles section

This is the recommended first choice for styling in block themes. Rather than writing raw CSS, you define styles as structured JSON properties — colors, typography, spacing, borders, shadows, and more — at the global, element, or per-block level.

You can use hardcoded values directly in styles, and they work just fine:

JSON

{
  "version": 3,
  "styles": {
    "color": {
      "background": "#ffffff",
      "text": "#333333"
    },
    "elements": {
      "link": {
        "color": { "text": "#0073aa" }
      }
    },
    "blocks": {
      "core/button": {
        "color": { "background": "#0073aa" }
      }
    }
  }
}

As your theme grows, hardcoded values become harder to manage. If you decide to change your primary color, you’d need to find and update every instance of #0073aa across styles. A more maintainable approach is to define your values as presets in the settings part of theme.json and then reference them in styles using the var:preset|| syntax. WordPress converts these presets into CSS custom properties (e.g., --wp--preset--color--primary), keeping everything connected:

JSON

{
  "version": 3,
  "settings": {
    "color": {
      "palette": [
        { "slug": "primary", "color": "#0073aa", "name": "Primary" },
        { "slug": "base", "color": "#ffffff", "name": "Base" },
        { "slug": "contrast", "color": "#333333", "name": "Contrast" }
      ]
    },
    "typography": {
      "fontSizes": [
        { "slug": "medium", "size": "18px", "name": "Medium" }
      ]
    }
  },
  "styles": {
    "color": {
      "background": "var:preset|color|base",
      "text": "var:preset|color|contrast"
    },
    "typography": {
      "fontSize": "var:preset|font-size|medium",
      "lineHeight": "1.6"
    },
    "elements": {
      "link": {
        "color": { "text": "var:preset|color|primary" }
      },
      "heading": {
        "typography": { "fontWeight": "700" }
      }
    },
    "blocks": {
      "core/button": {
        "border": { "radius": "4px" },
        "color": { "background": "var:preset|color|primary" }
      },
      "core/quote": {
        "typography": { "fontStyle": "italic" }
      }
    }
  }
}

This token-based approach pays off in a few ways. When a user changes “Primary” in the Global Styles UI, every element referencing that token updates automatically. It keeps your theme internally consistent — no risk of one button being #0073aa while another drifted to #0074ab. And it makes global style variations far simpler to create: a dark mode variation only needs to redefine the palette, not hunt down every color reference in styles. Either approach is valid, but tokens tend to save time as a theme matures.

Advantages:

Limitations:

Documentation

2. theme.json — The css Property

Who: Theme developers
Where: theme.jsonstyles.css (global) or styles.blocks.[blockname].css (per-block)

When theme.json’s structured properties aren’t enough, you can drop to raw CSS strings within theme.json itself. Available since WordPress 6.2.

Global custom CSS

JSON

{
  "version": 3,
  "styles": {
    "css": ".my-custom-class { opacity: 0.8; } body { scroll-behavior: smooth; }"
  }
}

Per-block custom CSS

JSON

{
  "version": 3,
    "styles": {    
       "blocks": {
          "core/image": {
              "css": "& img { border-radius: 8px; transition: transform 0.3s; } &:hover img { transform: scale(1.02); }"
              },     
          "core/post-title": {
              "css": "letter-spacing: 1px;"
             },
         "core/paragraph": {       
              "css": "&.has-background { padding: .5rem .8rem; }"
           }
        }
    }
}

WordPress auto-generates the selector for per-block CSS—you don’t need to know the block’s class name. The & selector is supported for targeting nested elements and pseudo-selectors (:before, :after, :hover, :not(), etc.).

Advantages:

Limitations:

Documentation

3. Custom CSS Properties (Variables) via theme.json Settings

Who: Theme developers
Where: theme.jsonsettings.custom

Create your own CSS custom properties that can be used anywhere in your CSS—in stylesheets, block stylesheets, or even within the css property.

JSON

{
  "version": 3,
  "settings": {
    "custom": {
      "contentMaxWidth": "800px",
      "spacing": {
        "gutter": "2rem"
      },
      "transition": "all 0.3s ease"
    }
  }
}

This generates:

JSON

body {
  --wp--custom--content-max-width: 800px;
  --wp--custom--spacing--gutter: 2rem;
  --wp--custom--transition: all 0.3s ease;
}

Advantages:

Limitations:

Documentation

4. Child Theme’s theme.json

Who: Developers customizing a parent theme
Where: theme.json in the child theme root

Override or extend the parent theme’s theme.json with a child theme. WordPress merges both files automatically.

Advantages:

Limitations:

Documentation

5. Global Styles UI

Who: Site owners, content editors (no code required)
Where: Appearance → Editor → Styles

The visual interface for customizing your site’s appearance. Users can adjust typography, colors, spacing, and layout for the entire site, individual elements, or specific block types — all without writing any code.

The Style Book helps you keep the overall context for your styles and you and double-check on how blocks behave when Styles change.

Advantages:

Limitations:

6. Additional CSS

Who: Site owners with CSS knowledge
Where: Appearance → Editor → Styles → three-dot menu (⋮) → Additional CSS

This is the block theme replacement for the classic Customizer’s Additional CSS panel. A free-form CSS editor for site-wide custom styles.

Advantages:

Limitations:

7. Per-Block Additional CSS

Who: Site owners with CSS knowledge
Where: Appearance → Editor → Styles → Blocks → select a block → scroll to Advanced → Additional CSS

Target CSS to a specific block type site-wide. WordPress auto-generates the selector — you just write the CSS properties.

CSS

/* Example: added to Styles → Blocks → Quote → Additional CSS */
font-style: italic;
border-left: 4px solid currentColor;
padding-left: 1.5em;

The & selector works here for nested elements and pseudo-selectors.

Advantages:

Limitations:

8. Additional CSS Class(es) on individual block instances

Who: Content editors with CSS knowledge
Where: Select any block in the editor → Settings sidebar → Advanced → Additional CSS Class(es)

Add custom class names to a specific block instance. The CSS itself must be defined elsewhere (style.css, block stylesheets, Additional CSS, etc.). As a user the global Additional CSS is probably the most practical place.

Advantages:

Limitations:

9. Global Style Variations (Theme-Level Style Presets)

Who: Theme developers
Where: JSON files in the theme’s /styles directory (at the top level, not in a /blocks subfolder)

Provide entire alternative site-wide style schemes. Each variation is a JSON partial that can override settings and styles from the main theme.json.

Since WordPress 6.6, style variations are further classified into three subtypes based on their content.

A variation file that defines only settings.color or styles.color is automatically recognized as a color variation, appearing under Styles → Colors → Palette.

One that defines only settings.typography or styles.typography becomes a typography variation, appearing under Styles → Typography → Presets.

Files that touch other properties remain full theme style variations.

Color and typography variations can be mixed and matched with each other and with theme variations.

To keep the /styles folder organized, it’s recommended to use subfolders (e.g., /styles/color, /styles/typography, /styles/global).

JSON

// /styles/dark-mode.json
{
  "$schema": "https://schemas.wp.org/trunk/theme.json",
  "version": 3,
  "title": "Dark Mode",
  "settings": {
    "color": {
      "palette": [
        { "slug": "base", "color": "#1a1a2e", "name": "Base" },
        { "slug": "contrast", "color": "#e0e0e0", "name": "Contrast" }
      ]
    }
  },
  "styles": {
    "color": {
      "background": "#1a1a2e",
      "text": "#e0e0e0"
    }
  }
}

Advantages:

Limitations:

Documentation

10. Block Style Variations (JSON, PHP, and JavaScript)

Who: Theme developers, plugin developers
Where: JSON files in /styles, PHP via register_block_style(), or JavaScript via wp.blocks.registerBlockStyle()

Block style variations give content creators pre-made styling options in the Styles panel — like “Rounded” or “Outline” on core blocks. You can register them via JSON files in /styles (WordPress 6.6+, no PHP needed), PHP with register_block_style(), or JavaScript. The JSON-based methods integrate with Global Styles so users can customize them in the Site Editor; inline_style, style_handle, and JavaScript methods do not.

For a thorough walkthrough of all six registration methods — with code examples, a companion theme and plugin on GitHub, and a comparison table showing which methods support Global Styles — see the comprehensive guide Mastering Custom Block Styles in WordPress: 6 Methods for Theme and Plugin Developers.

11. Section Styles

Who: Theme developers
Where: JSON files in /styles directory, targeting mostly group or column blocks.

Section styles are more of a convention than a separate feature. They are essentially Block Style Variations. The difference is naming the variation slug: style-1, style-2, etc. so that they can be reused across themes.

Since WordPress 6.6, block style variations can style nested inner blocks and elements, enabling “section styling” — where a Group block’s style variation changes the appearance of all blocks within it.

The Twenty Twenty Five theme showcases this convention quite nicely.

Advantages:

Limitations:

Documentation

12. Block Stylesheets via wp_enqueue_block_style()

Who: Theme developers
Where: Separate .css files in your theme (e.g., /assets/blocks/core-image.css), registered in functions.php

This is the recommended approach when you have more CSS than what’s comfortable in theme.json’s css property.

JSON

/* /assets/blocks/core-image.css */
.wp-block-image img {
    padding: 1rem;
    background: linear-gradient(-60deg, #ff5858, #f09819);
}
JSON

// functions.php
add_action( 'init', function() {
    wp_enqueue_block_style( 'core/image', array(
        'handle' => 'my-theme-image-styles',
        'src'    => get_theme_file_uri( 'assets/blocks/core-image.css' ),
        'path'   => get_theme_file_path( 'assets/blocks/core-image.css' ),
    ) );
});

Advantages:

Limitations:

Documentation

13. Enqueuing Stylesheets via wp_enqueue_style() in PHP

Who: Theme and plugin developers
Where: functions.php using the wp_enqueue_scripts (front end) and enqueue_block_editor_assets (editor) hooks

The traditional WordPress approach still works and is useful for loading third-party CSS libraries, fonts, or complex stylesheets.

PHP

add_action( 'wp_enqueue_scripts', function() {
    wp_enqueue_style(
        'my-theme-custom',
        get_theme_file_uri( 'assets/css/custom.css' ),
        array(),
        '1.0.0'
    );
});

// For editor-only styles:
add_action( 'enqueue_block_editor_assets', function() {
    wp_enqueue_style(
        'my-theme-editor',
        get_theme_file_uri( 'assets/css/editor.css' ),
        array(),
        '1.0.0'
    );
});

Advantages:

Limitations:

Documentation

What’s the difference between wp_enqueue_style and add_editor_style?

wp_enqueue_style() is the general-purpose function for loading stylesheets — you choose where it loads by picking your hook (wp_enqueue_scripts for the frontend, enqueue_block_editor_assets for the editor). When you use it on the editor hook, the stylesheet loads into the entire editor page, including the UI chrome (sidebar, toolbar, etc.).

add_editor_style() is specifically designed for visual parity between the editor and the frontend. WordPress takes the stylesheet you pass it and automatically scopes every selector to .editor-styles-wrapper, so your styles only affect the content canvas and don’t bleed into the editor UI. In block themes it’s typically called from an after_setup_theme hook — this is how themes get their style.css to apply inside the editor.

The practical upshot: if you want your theme’s frontend styles to look the same in the editor, use add_editor_style(). If you need to style the editor UI itself (like a custom sidebar panel) or need fine-grained control over loading conditions, dependencies, and versioning, use wp_enqueue_style() on the appropriate hook.

14. The Theme’s style.css

Who: Theme developers
Where: style.css in the theme root

The traditional main stylesheet still works in block themes, but its role has changed significantly. It now primarily holds theme metadata (name, version, description) and is loaded globally.

In block themes it’s typically called from an after_setup_theme hook — this is how themes get their style.css to apply inside the editor.

PHP

add_action( 'after_setup_theme', 'theme_slug_setup' );

function theme_slug_setup() {
	add_editor_style( get_stylesheet_uri() );
}

Advantages:

Limitations:

Documentation

Summary Table

# Method Who Scope Loads When Editable by Users
1 theme.json structured properties Developer Global / block / element Always (inlined) Yes, via Global Styles UI
2 theme.json css property Developer Global or per-block Always (inlined) Yes, per-block CSS is visible in Styles
3 Custom CSS properties (settings.custom) Developer Global (variables) Always No (indirectly via tokens)
4 Child theme.json Developer Inherits parent Same as parent Same as parent
5 Global Styles UI (visual) User Global / block / element Always (inlined) It is the user editing
6 Additional CSS (site-wide) User Global Every page Yes
7 Per-block Additional CSS User Per-block type When block present Yes
8 Additional CSS Class(es) User Single block instance When that block is present N/A (class only)
9 Global style variations Developer Whole site preset When active Users switch presets
10 Block style variations (JSON, PHP, JS) Developer Per-block variation Only when block + variation is used JSON/style_data methods: yes; inline_style/JS: no
11 Section styles Developer container blocks When block + variation is used Yes, via Styles panel
12 Block stylesheets (wp_enqueue_block_style) Developer Per-block Only when block is present No
13 wp_enqueue_style() Developer Configurable Configurable No
14 style.css Developer Global Every page No

Decision Guide

Start here: Use theme.json structured properties (#1) for everything they support. This gives you the best integration with the Global Styles system and lets users customize your styles.

Need CSS that theme.json properties can’t express? Use the theme.json css property (#2) for small additions or block stylesheets (#12) for anything substantial.

Want to offer style choices to content editors? Create block style variations (#10) so they appear in the Styles panel.

Site owner making tweaks? Use the Global Styles UI (#5) first, then fall back to Additional CSS (#7 or #7) for anything the visual interface can’t handle.

Need to target one specific block instance? Use Additional CSS Class(es) (#8) and define the class in your preferred CSS source.

Building a child theme? Override the parent via child theme.json (#4) and add block stylesheets as needed.

Need to sync user edits with theme files? Use the Create Block Theme plugin.

How to sync user editor to theme files and maintain version control? Here is a tutorial: Streamlining block theme development with WordPress Playground and GitHub.


Huge Thank you to Justin Tadlock for reviewing the post to make sure it’s all technically accurate.

Exit mobile version